package com.xxl.job.core.executor.impl;

import com.xxl.job.core.executor.XxlJobExecutor;
import com.xxl.job.core.glue.GlueFactory;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.xxl.job.core.handler.impl.MethodJobHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;

import java.lang.reflect.Method;
import java.util.Map;


/**
 * xxl-job executor (for spring)
 *
 * @author xuxueli 2018-11-01 09:24:52
 */
/**
 * 自己闲来无事，写一下 ApplicationContextAware，SmartInitializingSingleton，DisposableBean的作用：其实不太懂Spring源码
 *
 * ApplicationContextAware：
 *      接口，该接口将由任何希望收到其运行的ApplicationContext通知的对象实现。
 *      例如，当一个对象需要访问一组协作bean时，实现这个接口是有意义的。请注意，通过bean引用进行配置比仅为bean查找目的实现此接口更可取。
 *      如果一个对象需要访问文件资源，也就是说，想要调用getResource，想要发布应用程序事件，或者需要访问MessageSource，那么这个接口也可以实现。
 *      但是，最好在这种特定场景中实现更具体的ResourceLoaderware、ApplicationEventPublisherAware或MessageSourceAware接口。
 *
 *      请注意，文件资源依赖项也可以公开为resource类型的bean属性，由bean工厂通过自动类型转换的字符串填充。这样就不需要为了访问特定文件资源而实现任何回调接口。
 *      ApplicationObjectSupport是一个方便的应用程序对象基类，实现了这个接口。
 *      有关所有bean生命周期方法的列表，请参阅BeanFactory javadocs。
 *
 *      void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
 *          包含BeanFactory的程序在设置了所有bean属性并满足BeanFactoryAware、ApplicationContextAware等条件后调用。
 *·         此方法允许bean实例在设置了所有bean属性后执行其总体配置验证和最终初始化
 *
 *
 * SmartInitializingSingleton ：
 *      在BeanFactory引导过程中，在单例预实例化阶段结束时触发回调接口。
 *      这个接口可以由单例bean实现，以便在常规单例实例化算法之后执行一些初始化，
 *      避免意外的早期初始化（例如，从ListableBeanFactory.getBeansOfType（java.lang.Class<T>）调用）带来的副作用。
 *      从这个意义上说，它是初始化bean的一种替代方法，初始化bean在bean的本地构造阶段结束时被触发。
 *
 *      此回调变量与org.springframework.context.event.ContextRefreshedEvent有些类似，
 *      但不需要org.springframework.context.ApplicationListener的实现，不需要跨上下文层次结构等过滤上下文引用。
 *      它还意味着仅对beans包的依赖性更小，并且独立的ListableBeanFactory实现（而不仅仅是在org.springframework.context.ApplicationContext环境中）也能满足这一要求。
 *
 *      注意：如果您打算启动/管理异步任务，最好实现org.springframework.context.Lifecycle，它为运行时管理提供了更丰富的模型，并允许分阶段启动/关闭。
 *
 *      void afterSingletonsInstantiated();
 *          在单例预实例化阶段结束时调用，并保证已经创建了所有常规单例bean。此方法中的ListableBeanFactory.getBeansOfType（java.lang.Class<T>）调用在引导过程中不会触发意外的副作用。
 *          注意：对于在BeanFactory引导后按需延迟初始化的单例bean，以及任何其他bean作用域，都不会触发此回调。小心地将其仅用于具有预期引导语义的bean。
 *
 * DisposableBean：
 *      接口将由希望在销毁时释放资源的bean实现。BeanFactory将在单独销毁作用域bean时调用destroy方法。
 *      org.springframework.context.ApplicationContext应该在应用程序生命周期驱动下，在关闭时处理其所有单例。
 *      出于同样的目的，Spring管理的bean也可以实现Java的自动关闭接口。实现接口的另一种方法是指定自定义销毁方法，例如在XMLBean定义中。
 *
 *      void destroy() throws Exception;
*              在销毁bean时由包含BeanFactory调用。
*              抛出：
 *                异常-在关闭错误的情况下。异常将被记录，但不会被重新调用，以允许其他bean也释放它们的资源。
 *
 */
public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationContextAware, SmartInitializingSingleton, DisposableBean {
    private static final Logger logger = LoggerFactory.getLogger(XxlJobSpringExecutor.class);


    /**
     * 这个接口中只有一个方法afterSingletonsInstantiated，其作用是是 在spring容器管理的所有单例对象（非懒加载对象）初始化完成之后调用的回调接口。
     * 其触发时机为postProcessAfterInitialization之后。
     * 使用场景：用户可以扩展此接口在对所有单例对象初始化完毕后，做一些后置的业务处理。
     */
    // start
    @Override
    public void afterSingletonsInstantiated() {

        // init JobHandler Repository
        /*initJobHandlerRepository(applicationContext);*/

        // init JobHandler Repository (for method)
        initJobHandlerMethodRepository(applicationContext);

        // refresh GlueFactory
        GlueFactory.refreshInstance(1);

        // super start
        try {
            super.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // destroy
    @Override
    public void destroy() {
        super.destroy();
    }


    /*private void initJobHandlerRepository(ApplicationContext applicationContext) {
        if (applicationContext == null) {
            return;
        }

        // init job handler action
        Map<String, Object> serviceBeanMap = applicationContext.getBeansWithAnnotation(JobHandler.class);

        if (serviceBeanMap != null && serviceBeanMap.size() > 0) {
            for (Object serviceBean : serviceBeanMap.values()) {
                if (serviceBean instanceof IJobHandler) {
                    String name = serviceBean.getClass().getAnnotation(JobHandler.class).value();
                    IJobHandler handler = (IJobHandler) serviceBean;
                    if (loadJobHandler(name) != null) {
                        throw new RuntimeException("xxl-job jobhandler[" + name + "] naming conflicts.");
                    }
                    registJobHandler(name, handler);
                }
            }
        }
    }*/

    private void initJobHandlerMethodRepository(ApplicationContext applicationContext) {
        if (applicationContext == null) {
            return;
        }
        // init beanDefinitionNames = {String[158]@5691} ["org.springframe...", "org.springframe...", "org.springframe...", "org.springframe...", "org.springframe...", +153 more]job handler from method
        String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, true);
        for (String beanDefinitionName : beanDefinitionNames) {
            //这里就是为了寻找 方法上打了注解 @XxlJob 的类(bean)
            Object bean = applicationContext.getBean(beanDefinitionName);

            Map<Method, XxlJob> annotatedMethods = null;   // referred to ：org.springframework.context.event.EventListenerMethodProcessor.processBean
            try {
                annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),
                        new MethodIntrospector.MetadataLookup<XxlJob>() {
                            @Override
                            public XxlJob inspect(Method method) {
                                return AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class);
                            }
                        });
            } catch (Throwable ex) {
                logger.error("xxl-job method-jobhandler resolve error for bean[" + beanDefinitionName + "].", ex);
            }
            if (annotatedMethods==null || annotatedMethods.isEmpty()) {
                continue;
            }

            //将该类中的所有 打上了 @XxlJob 的方法进行注册
            for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {
                Method executeMethod = methodXxlJobEntry.getKey();
                XxlJob xxlJob = methodXxlJobEntry.getValue();
                if (xxlJob == null) {
                    continue;
                }

                String name = xxlJob.value();
                if (name.trim().length() == 0) {
                    throw new RuntimeException("xxl-job method-jobhandler name invalid, for[" + bean.getClass() + "#" + executeMethod.getName() + "] .");
                }
                if (loadJobHandler(name) != null) {
                    throw new RuntimeException("xxl-job jobhandler[" + name + "] naming conflicts.");
                }

                // execute method
                /*if (!(method.getParameterTypes().length == 1 && method.getParameterTypes()[0].isAssignableFrom(String.class))) {
                    throw new RuntimeException("xxl-job method-jobhandler param-classtype invalid, for[" + bean.getClass() + "#" + method.getName() + "] , " +
                            "The correct method format like \" public ReturnT<String> execute(String param) \" .");
                }
                if (!method.getReturnType().isAssignableFrom(ReturnT.class)) {
                    throw new RuntimeException("xxl-job method-jobhandler return-classtype invalid, for[" + bean.getClass() + "#" + method.getName() + "] , " +
                            "The correct method format like \" public ReturnT<String> execute(String param) \" .");
                }*/

                executeMethod.setAccessible(true);

                // init and destory
                Method initMethod = null;
                Method destroyMethod = null;

                if (xxlJob.init().trim().length() > 0) {
                    try {
                        initMethod = bean.getClass().getDeclaredMethod(xxlJob.init());
                        initMethod.setAccessible(true);
                    } catch (NoSuchMethodException e) {
                        throw new RuntimeException("xxl-job method-jobhandler initMethod invalid, for[" + bean.getClass() + "#" + executeMethod.getName() + "] .");
                    }
                }
                if (xxlJob.destroy().trim().length() > 0) {
                    try {
                        destroyMethod = bean.getClass().getDeclaredMethod(xxlJob.destroy());
                        destroyMethod.setAccessible(true);
                    } catch (NoSuchMethodException e) {
                        throw new RuntimeException("xxl-job method-jobhandler destroyMethod invalid, for[" + bean.getClass() + "#" + executeMethod.getName() + "] .");
                    }
                }

                // registry jobhandler
                // MethodJobHandler 对类信息，方法信息进行包装
                // 这里注册，其实就是将其放到 map 中
                registJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));
            }
        }

    }

    // ---------------------- applicationContext ----------------------
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

}
